home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 8004 / 8004.xpi / content / chromatabs.js next >
Encoding:
JavaScript  |  2010-01-30  |  30.7 KB  |  882 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is Chromatabs.
  15.  *
  16.  * The Initial Developer of the Original Code is Mozilla.
  17.  * Portions created by the Initial Developer are Copyright (C) 2006
  18.  * the Initial Developer. All Rights Reserved.
  19.  *
  20.  * Contributor(s):
  21.  *  Justin Dolske <dolske@mozilla.com> (Original Author)
  22.  *  Gary Calpo <gcalpo@gmail.com> (Updated to work w/ Firefox 3.0-3.6)
  23.  *
  24.  * Alternatively, the contents of this file may be used under the terms of
  25.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  26.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  27.  * in which case the provisions of the GPL or the LGPL are applicable instead
  28.  * of those above. If you wish to allow use of your version of this file only
  29.  * under the terms of either the GPL or the LGPL, and not to allow others to
  30.  * use your version of this file under the terms of the MPL, indicate your
  31.  * decision by deleting the provisions above and replace them with the notice
  32.  * and other provisions required by the GPL or the LGPL. If you do not delete
  33.  * the provisions above, a recipient may use your version of this file under
  34.  * the terms of any one of the MPL, the GPL or the LGPL.
  35.  *
  36.  * ***** END LICENSE BLOCK ***** */
  37. var CHROMATABS_SS  = Components.classes["@mozilla.org/browser/sessionstore;1"].getService(Components.interfaces.nsISessionStore);
  38.  
  39. var CHROMATABS = {
  40.  
  41.     _logService  : null,
  42.     _debug       : null,
  43.     _prefs       : null,
  44.     _canvas      : null,
  45.  
  46.     _colorMode   : null,
  47.     _hashFallback : null,
  48.     
  49.     // 2.3.0: New settings
  50.     
  51.     _hueTolerance: 5,  // pixel B's hue can be off by X% from pixel A but still count towards  pixel A's tally in icon-color-frequency mode
  52.     _clearTabCloseButtons: null,
  53.  
  54.     lastSelectedTab   : null,
  55.  
  56.  
  57.     /*
  58.      * log
  59.      *
  60.      * Log debug messages to the Javascript console.
  61.      * Note: javascript.options.showInConsole must be enabled
  62.      */
  63.     log : function (message) {
  64.         if (!this._debug) { return; }
  65.         this._logService.logStringMessage("Chromatabs: " + message);
  66.     },
  67.  
  68.  
  69.    /*
  70.     * _locationListener
  71.     *
  72.     * Installed on each tab so we know when the URL changes.
  73.     */
  74.     _locationListener : {
  75.         QueryInterface : function (aIID) {
  76.                 if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||
  77.                     aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
  78.                     aIID.equals(Components.interfaces.nsISupports))
  79.                         return this;
  80.                 throw Components.results.NS_NOINTERFACE;
  81.         },
  82.  
  83.         onLocationChange : function (aProgress, aRequest, aURI) {
  84.  
  85.         var doc = aProgress.DOMWindow.document;
  86.         var tab = gBrowser.mTabs[gBrowser.getBrowserIndexForDocument(doc)];
  87.  
  88.         var oldLocation = tab.getAttribute("x-chromatabs-lastLocation");
  89.         var newLocation = CHROMATABS.getHostnameForTab(tab);
  90.         tab.setAttribute("x-chromatabs-lastLocation", newLocation);
  91.         CHROMATABS.log("location change: " + newLocation);
  92.  
  93.         // Avoid recolorizing the tab if we didn't change sites (which would hash the same).
  94.         // The same site could have different favicons, but if we're using those the
  95.         // onLinkiconAvailable code will handle changes.
  96.         if (newLocation != oldLocation) {
  97.             CHROMATABS.colorizeTab(tab, false);
  98.         }
  99.         },
  100.  
  101.         onLinkIconAvailable: function(aBrowser) {
  102.         CHROMATABS.log("onLinkIcon...");
  103.         if (CHROMATABS._colorMode != "icon" && CHROMATABS._colorMode != "icon-frequency" && CHROMATABS._colorMode != "icon-average") { return; }
  104.  
  105.         var doc = aBrowser.contentDocument;
  106.         var tab = gBrowser.mTabs[gBrowser.getBrowserIndexForDocument(doc)];
  107.  
  108.         // Whenever a page loads, we always seem to be called once with no icon (throbber?),
  109.         // and then again with an icon. If we navigate to a page without a favicon, we'll rely
  110.         // on onLocationChange to reset the tab color.
  111.         var newIcon = CHROMATABS.getFaviconURL(tab);
  112.         if (!newIcon) { return; }
  113.  
  114.         var oldIcon = tab.getAttribute("x-chromatabs-lastIcon");
  115.         tab.setAttribute("x-chromatabs-lastIcon", newIcon);
  116.  
  117.         if (newIcon != oldIcon) {
  118.             CHROMATABS.colorizeTab(tab, false);
  119.         }
  120.     },
  121.  
  122.         // unused stubs
  123.         onStateChange:       function() { return 0; },
  124.         onProgressChange:    function() { return 0; },
  125.         onStatusChange:      function() { return 0; },
  126.         onSecurityChange:    function() { return 0; }
  127.     },
  128.  
  129.  
  130.     /*
  131.      * _prefObserver
  132.      */
  133.     _prefObserver : {
  134.  
  135.     QueryInterface : function (iid) {
  136.         const interfaces = [Ci.nsIObserver, Ci.nsISupports, Ci.nsISupportsWeakReference];
  137.         if (!interfaces.some( function(v) { return iid.equals(v) } ))
  138.             throw Components.results.NS_ERROR_NO_INTERFACE;
  139.         return this;
  140.     },
  141.  
  142.     observe : function (subject, topic, data) {
  143.         if (topic != "nsPref:changed") { throw "Woah, unexpected observer invocation."; }
  144.         // One of our prefs changes. We'll just be lazy and set them all.
  145.         CHROMATABS._debug        = CHROMATABS._prefs.getBoolPref("debug");
  146.         CHROMATABS._colorMode    = CHROMATABS._prefs.getCharPref("colorMode");
  147.         CHROMATABS._hashFallback = CHROMATABS._prefs.getBoolPref("hashFallback");
  148.         CHROMATABS._clearTabCloseButtons = CHROMATABS._prefs.getBoolPref("clearTabCloseButtons");
  149.         
  150.         // 2.3.0: repaint all tabs and redraw close buttons
  151.         CHROMATABS.log("Repainting all tabs...");
  152.         CHROMATABS.colorizeAllTabs();
  153.         CHROMATABS.log("Restyling close buttons tabs...");
  154.         CHROMATABS.restyleCloseButtons();
  155.  
  156.     }
  157.     },
  158.  
  159.     /*
  160.      * init
  161.      *
  162.      * Chromatabs initialization. Called when once, when a window opens.
  163.      */
  164.     init : function () {
  165.     this._logService = Components.classes['@mozilla.org/consoleservice;1'].getService();
  166.     this._logService.QueryInterface(Components.interfaces.nsIConsoleService);
  167.  
  168.         this._prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(
  169.                                 Components.interfaces.nsIPrefService).getBranch("extensions.chromatabs.");
  170.     this._prefs.QueryInterface(Components.interfaces.nsIPrefBranch2);
  171.     this._prefs.addObserver("", this._prefObserver, false);
  172.  
  173.     this._debug     = this._prefs.getBoolPref("debug");
  174.     this._colorMode    = this._prefs.getCharPref("colorMode");
  175.     this._hashFallback = this._prefs.getBoolPref("hashFallback");
  176.     this._clearTabCloseButtons = this._prefs.getBoolPref("clearTabCloseButtons");
  177.     
  178.     if (this._colorMode == "icon") {
  179.         // use new colorMode
  180.         this._colorMode == "icon-average"
  181.         this._prefs.setCharPref("colorMode", "icon-average");
  182.     }
  183.     
  184.  
  185.     this.log("Chromatabs initializing...");
  186.  
  187.     // We'll be needing a <canvas> element to do the image processing... It requires a
  188.     // docshell (?) to work, so we'll stash it inside a hidden <iframe>.
  189.  
  190.     // Create an iframe, make it hidden, and secure it against untrusted content.
  191.     var iframe = document.createElement('iframe');
  192.     iframe.setAttribute("type", "content");
  193.  
  194.   
  195.     // Insert the iframe into the window, creating the doc shell.
  196.     document.documentElement.appendChild(iframe);
  197.  
  198.     // When we insert the iframe into the window, it immediately starts loading
  199.     // about:blank, which we don't need and could even hurt us (for example
  200.     // by triggering bugs like bug 344305), so cancel that load.
  201.     var webNav = iframe.docShell.QueryInterface(Components.interfaces.nsIWebNavigation);
  202.     webNav.stop(Ci.nsIWebNavigation.STOP_NETWORK);
  203.  
  204.     // TODO: trunk hack to avoid flashing white in content bug?
  205.     iframe.setAttribute("hidden", "true");
  206.     //iframe.setAttribute("collapsed", true);
  207.  
  208.     this._canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
  209.     iframe.appendChild(this._canvas);
  210.  
  211.     // Add listeners for the various tab events
  212.     gBrowser.addEventListener("TabOpen",   this.onTabOpen,   false); // 2.2.0: uncommented to colorize tabs opened via Open Link In New Tab
  213.     gBrowser.addEventListener("SSTabRestoring",this.onTabRestore, false);
  214.     gBrowser.addEventListener("TabMove",   this.onTabMove,   false);
  215.     //gBrowser.addEventListener("TabClose",  this.onTabClose,  false);
  216.     gBrowser.addEventListener("TabSelect", this.onTabSelect, false);
  217.  
  218.     // Watch for mouse in/out so we can handle CSS :hover rules
  219.     gBrowser.mStrip.addEventListener("mouseover", CHROMATABS.onMouseInEvent,  false);
  220.     gBrowser.mStrip.addEventListener("mouseout",  CHROMATABS.onMouseOutEvent, false);
  221.  
  222.     // Listen for location changes and favicon loads.
  223.     gBrowser.addProgressListener(CHROMATABS._locationListener);
  224.  
  225.     // The initial tab is selected.
  226.     CHROMATABS.lastSelectedTab = gBrowser.mTabs[0];
  227.     
  228.     // Apply close button style, if desired
  229.     CHROMATABS.restyleCloseButtons();
  230.  
  231.     },
  232.  
  233.  
  234.     onTabOpen: function (event) {
  235.     // The location listener will take care of colorizing the tab.
  236.     // When using "Open In New Tab", the location listener doesn't catch the new tab, so this is necessary.
  237.     var tab = event.originalTarget;
  238.     tab.setAttribute("onload","CHROMATABS.colorizeTab(this, false);")
  239.     //tab.setAttribute("onerror","CHROMATABS.log('icon load failure');")
  240.     
  241.  
  242.     },
  243.  
  244.     onTabRestore: function (event) {
  245.     // Restoring a tab from a previous session...
  246.     CHROMATABS.log("restored tab");
  247.  
  248.     var tab = event.originalTarget;
  249.     var savedColor = CHROMATABS_SS.getTabValue(tab, "chromatabsColor");
  250.     CHROMATABS.setTabColor(tab, savedColor);
  251.     //CHROMATABS.colorizeTab(tab, true); 
  252.     
  253.  
  254.     },
  255.  
  256.  
  257.     onTabMove: function (event) {
  258.     CHROMATABS.log("tab moved");
  259.  
  260.     var tab = event.originalTarget;
  261.     //var browser = tab.linkedBrowser;
  262.  
  263.     // XXX - bug? the event listeners seem to disappear when the tab is moved!
  264.     // ...fixed by moving listeners to tab strip. (re-adding them also worked)
  265.  
  266.     // You'd kinda think it would stay colored, but it doesn't.
  267.     CHROMATABS.colorizeTab(tab, false);
  268.     },
  269.  
  270.  
  271.     onTabClose: function (event) {
  272.     // (No tab to colorize, obviously.)
  273.     },
  274.  
  275.  
  276.     onTabSelect: function (event) {
  277.     CHROMATABS.log("tab selected");
  278.  
  279.     // Recolor the tab that just got deselected
  280.     var oldTab = CHROMATABS.lastSelectedTab;
  281.  
  282.     var tab = event.originalTarget;
  283.     CHROMATABS.lastSelectedTab = tab;
  284.  
  285.     if (oldTab) {
  286.         CHROMATABS.log("deselecting last tab");
  287.         CHROMATABS.colorizeTab(oldTab, false);
  288.     }
  289.  
  290.     CHROMATABS.colorizeTab(tab, false);
  291.     },
  292.  
  293.  
  294.     onMouseInEvent : function (event) {
  295.     // Ignore events targeted at other bits of the tabstrip
  296.     // XXX - Weird. The first tab's (from startup) event gives us "xul:tab", others seem to be just "tab".
  297.     if (event.target.nodeName != "xul:tab" && event.target.nodeName != "tab") { return; }
  298.  
  299.     //CHROMATABS.log("mouse IN event to " + event.target.nodeName);
  300.  
  301.     var tab = event.target;
  302.  
  303.     // Make sure the UI stays in sync, by canceling any pending delayed-mouseout 
  304.     var delayID = tab.getAttribute("x-chromatabs-delayid");
  305.     if (delayID) {
  306.         CHROMATABS.log("clearing pending delayed-mouseout handler (delayID=" + delayID + ")");
  307.         clearTimeout(delayID);
  308.         tab.setAttribute("x-chromatabs-delayid", null);
  309.     }
  310.     tab.hovered = true; // 2.3.0: track hover state
  311.     CHROMATABS.colorizeTab(tab, false);
  312.     },
  313.  
  314.  
  315.     /*
  316.      * onMouseOutEvent
  317.      *
  318.      * Ideally, this should be a simple function like onMouseInEvent. Unfortunately,
  319.      * when we call getComputedStyle() we still get the style for the hovered element.
  320.      * In other words, CSS thinks the mouse is still over the tab and so we get the
  321.      * wrong set of background images.
  322.      *
  323.      * As a workaround, triggered ourself as a callback, delayed by 1ms. That seems to
  324.      * allow things to get back in sync.
  325.      *
  326.      * XXX - The delay also results in an undesirable flicker, probably between when CSS
  327.      * sets the unhovered image and our delayed handler fires.
  328.      */ 
  329.     onMouseOutEvent : function (event, delayed, realtab) {
  330.  
  331.     // Ignore events targeted at other bits of the tabstrip
  332.     if (!delayed && event.target.nodeName != "xul:tab" && event.target.nodeName != "tab") { return; }
  333.  
  334.     //CHROMATABS.log("mouse OUT event to " + event.target.nodeName + (delayed ? " (delayed event)" : ""));
  335.  
  336.     var tab = event.target;
  337.  
  338.     // XXX - Hmm, "event" is sometimes wrong when the delayed handler is invoked. (eg, it's an xul:label or xul:hbox)
  339.     // Not sure I understand exactly why that is, but explicitly passing along the tab in "realtab" seems to work.
  340.     // (Maybe the Event is being reused, and the target being changed as it captures/bubbles?)
  341.     if (delayed) { tab = realtab; }
  342.  
  343.     var delayID = tab.getAttribute("x-chromatabs-delayid");
  344.  
  345.     // If we are a delayed event (see below), don't do anything if some other in/out event
  346.     // has already executed and cleared the delayID.
  347.     if (delayed) {
  348.         if (!delayID) {
  349.             CHROMATABS.log("delayed mouseout is unneeded.");
  350.             return;
  351.         }
  352.  
  353.         CHROMATABS.log("delayed mouseout executing. (delayID=" + delayID + ")");
  354.         tab.hovered = false; // 2.3.0: track hover state
  355.         CHROMATABS.colorizeTab(tab, false);
  356.  
  357.         tab.setAttribute("x-chromatabs-delayid", null);
  358.  
  359.         return;
  360.     }
  361.  
  362.     // This shouldn't happen. If there's already a pending delated-mouseout, we shouldn't be getting
  363.     // this mouseout event without an interveining mousein, which would have cleared the delayID.
  364.     if (delayID) {
  365.         CHROMATABS.log("Oops! Unexpected mouseout! (delayID=" + delayID + ")");
  366.         return;
  367.     }
  368.  
  369.     CHROMATABS.log("delaying mouseout handling.");
  370.     delayID = setTimeout(CHROMATABS.onMouseOutEvent, 0, event, true, tab);
  371.     tab.setAttribute("x-chromatabs-delayid", delayID);
  372.     },
  373.  
  374.  
  375.     /*
  376.      * colorizeTab
  377.      *
  378.      * Given a <tab>, step into the it's implementation (anonymous nodes from XBL) and colorize each component.
  379.      * This works for the default theme, but some other themes may change the layout and break us.
  380.      */
  381.     colorizeTab : function (tab, byEventHandler, NoCache) {
  382.  
  383.     var color, node;
  384.     var doFallback = false;
  385.     CHROMATABS.log("Colorizing tab[" +tab._tPos + "] " + (byEventHandler ? " (ASYNC) " : " ") + this._colorMode);
  386.  
  387.     var doc = tab.ownerDocument;
  388.  
  389.     if (this._colorMode == "icon" || this._colorMode == "icon-frequency" || this._colorMode == "icon-average") {
  390.         color = CHROMATABS.getFaviconColor(tab, byEventHandler, NoCache);
  391.         CHROMATABS.log("Got favicon color: " + color);
  392.  
  393.         // Ignore icons with no color.
  394.         if (color == "rgba(0, 0, 0, 1)" || color == "rgba(255, 255, 255, 1)" || color == null) {
  395.             if (!this._hashFallback) { CHROMATABS.log("Not falling back.  Exiting colorizeTab."); return; }
  396.             doFallback = true;
  397.         }
  398.     }
  399.  
  400.     if (this._colorMode == "hash" || doFallback) {
  401.         CHROMATABS.log("Doing fall back...");
  402.         color = CHROMATABS.computeHostnameColor(tab);
  403.     }
  404.     
  405.     var minSaturation, maxSaturation, minLuminance, maxLuminance, opacity;
  406.     minSaturation = 0;
  407.     CHROMATABS.log("Hover check: " + tab.hovered);
  408.     if (tab.selected) {
  409.             maxSaturation = CHROMATABS._prefs.getIntPref("focusedTab.maxSaturation");
  410.             minLuminance = CHROMATABS._prefs.getIntPref("focusedTab.minLuminance");
  411.             maxLuminance = CHROMATABS._prefs.getIntPref("focusedTab.maxLuminance");
  412.             opacity = CHROMATABS._prefs.getIntPref("focusedTab.opacity");
  413.     } else if (tab.hovered)  {
  414.             maxSaturation = CHROMATABS._prefs.getIntPref("hoverTab.maxSaturation");
  415.             minLuminance = CHROMATABS._prefs.getIntPref("hoverTab.minLuminance");
  416.             maxLuminance = CHROMATABS._prefs.getIntPref("hoverTab.maxLuminance");
  417.             opacity = CHROMATABS._prefs.getIntPref("hoverTab.opacity");
  418.     } else {
  419.             maxSaturation = CHROMATABS._prefs.getIntPref("backgroundTabs.maxSaturation");
  420.             minLuminance = CHROMATABS._prefs.getIntPref("backgroundTabs.minLuminance");
  421.             maxLuminance = CHROMATABS._prefs.getIntPref("backgroundTabs.maxLuminance");
  422.             opacity = CHROMATABS._prefs.getIntPref("backgroundTabs.opacity");
  423.     }
  424.     
  425.     if (!color) return; // 2.3.0: prevent those pesky error messages about color being null
  426.  
  427.     // parse the "color" variable  which should be the form xxxx(R, G, B, 1);
  428.     CHROMATABS.log("Parsing color: " + color);
  429.     var temp = color.split(",");
  430.     var temp2  = temp[0].split("(");
  431.     var rgbRed = temp2[1] - 0, rgbGreen = temp[1] - 0, rgbBlue = temp[2] - 0;
  432.     CHROMATABS.log("Converting: " + rgbRed + ", " + rgbGreen + ", " + rgbBlue);
  433.     
  434.     var hsl = CHROMATABS.rgbToHsl(rgbRed, rgbGreen, rgbBlue);
  435.     var hue = hsl[0] * 360; // convert from [0,1] to [0, 360]
  436.     saturation = hsl[1] * 100; // convert from [0,1] to [0, 100]
  437.     luminance = hsl[2] * 100; // convert from [0,1] to [0, 100]
  438.     
  439.     // fit to tolerances
  440.     if (saturation < minSaturation) saturation = minSaturation;
  441.     if (saturation > maxSaturation) saturation = maxSaturation;
  442.     if (luminance < minLuminance) luminance = minLuminance;
  443.     if (luminance > maxLuminance) luminance = maxLuminance;
  444.     
  445.     CHROMATABS.log("Results of HSL: " + hsl[0] + ", " + hsl[1] + ", " + hsl[2]);
  446.     
  447.     color = "hsla(" + hue + ", " + saturation + "%, " + luminance + "%, " + (opacity / 100) + ")";
  448.  
  449.     /////////////////////////////////////////
  450.     // CODE BELOW SHOULD WORK IN FIREFOX 3 //
  451.     /////////////////////////////////////////
  452.     try {
  453.         CHROMATABS.setTabColor(tab, color);
  454.     } catch (e) { } 
  455.  
  456.     },
  457.     
  458.     setTabColor : function (tab, color) {
  459.         CHROMATABS.log("Setting tab color to: " + color);
  460.         CHROMATABS_SS.setTabValue(tab, "chromatabsColor", color);
  461.         tab.style.setProperty('background-color', color,'important'); 
  462.         CHROMATABS.colorBottomBar();
  463.         CHROMATABS.colorTreeStyleTabsBar();
  464.     },
  465.  
  466.  
  467.     getFaviconURL : function (tab) {
  468.     var imgsrc1 = tab.getAttribute("image");
  469.     // When we get an onLinkIconAvailable event, the image attribute doesn't seem to be set yet.
  470.     var imgsrc2 = (tab.linkedBrowser ? tab.linkedBrowser.mIconURL : null);
  471.  
  472.     this.log("tab.image = " + imgsrc1 + ", tab.mIconURL = " + imgsrc2);
  473.  
  474.     return (imgsrc1 ? imgsrc1 : imgsrc2);
  475.     },
  476.  
  477.     _iconColorCache : {},
  478.  
  479.     getFaviconColor : function (tab, byEvent, NoCache) {
  480.  
  481.     var imgsrc = this.getFaviconURL(tab);
  482.     if (!imgsrc) { return null; }
  483.  
  484.     // Check for a cached value
  485.     if (imgsrc in CHROMATABS._iconColorCache && !NoCache) {
  486.         var color = CHROMATABS._iconColorCache[imgsrc];
  487.         CHROMATABS.log("Using cached color for " + imgsrc);
  488.         return color;
  489.     }
  490.  
  491.     // No cached value, so we'll have to do things the slow way....
  492.     // Render the image in a <canvas>, and wade through the pixel data.
  493.     var canvas = CHROMATABS._canvas;
  494.     var ctx = canvas.getContext("2d");
  495.  
  496.     // Get the original image being used
  497.     var img = new Image();
  498.     img.src = imgsrc;
  499.     if (!img.complete) {
  500.         // The onload listener should always have the data, but if it somehow doesn't we'll just give up.
  501.         if (!byEvent) {
  502.             CHROMATABS.log("...favicon not yet loaded, deferring processing to onload handler.");
  503.             img.addEventListener("load", function() { CHROMATABS.colorizeTab(tab, true); }, false);
  504.         } else {
  505.             CHROMATABS.log("...favicon not yet loaded, but we're the onload handler! WTF!!!.");
  506.         }
  507.         return null;
  508.     }
  509.     
  510.     if (img.height == 0) {
  511.         // no image is loaded
  512.         CHROMATABS.log("No favicon at all!");
  513.         return null;
  514.     }
  515.  
  516.     // Set canvas size to favicon size (source image could be bigger, but we don't really need all of it.)
  517.     canvas.setAttribute("width", 16);
  518.     canvas.setAttribute("height", 16);
  519.     
  520.     // Draw original image to the canvas.
  521.     ctx.drawImage(img, 0, 0, 16, 16);
  522.  
  523.     // Get the raw pixels. This returns an array of integers, 4 per pixel.
  524.     // R, G, B, A,  R, G, B, A,  ...
  525.     var pixels = ctx.getImageData(0,0,16,16).data;
  526.  
  527.     // No need to look at every pixel
  528.     var stride = 3;
  529.  
  530.     // We'll use pixel (0,0) as a workpixel for compositing.
  531.     ctx.clearRect(0, 0, 1, 1);
  532.  
  533.     var pixelCount = 0;
  534.  
  535.     var arrColor = new Array();
  536.     var arrColorFreq = new Array();
  537.     var nrColors = 0;
  538.  
  539.     for (var i = 0; i < 16*16; i += stride) {
  540.         var p = 4*i;
  541.  
  542.         // Ignore pixels which are:
  543.         // - mostly transparent
  544.         // - almost white (many icons use a white background instead of being transparent)
  545.         // - almost black (probably background color, shadow, or other non-interesting part of image)
  546.         if (pixels[p+3] < 192) { continue; } // ignore pixels mostly transparent
  547.         if (pixels[p] < 30   && pixels[p+1] < 30   && pixels[p+2] < 30  ) { continue; } // ignore black pixels
  548.         if (pixels[p] > 230 && pixels[p+1] > 230 && pixels[p+2] > 230) { continue; } // ignore white pixels
  549.  
  550.         pixelCount++;
  551.  
  552.         // The logic here is sort of recursive... The Nth pixel should contribute (100/N)% of the
  553.         // color for an N-pixel image, when drawn on top of the average of the previous N-1 pixels.
  554.         //
  555.         // 1st pixel drawn has 100% opacity.
  556.         // 2nd pixel has 50% opacity,
  557.         // 3rd pixel has 33% opacity
  558.         // 4th pixel has 25% opacity, etc
  559.         ctx.globalAlpha = 1.0 / (pixelCount);
  560.  
  561.         // Ignore alpha of source pixel.
  562.         ctx.fillStyle = "rgba(" + pixels[p] + ", " + pixels[p+1] + ", "+ pixels[p+2] + ", 1)";
  563.  
  564.         // Draw the pixel onto workpixel, letting canvas handle the compositing math.
  565.         ctx.fillRect(0, 0, 1, 1);
  566.         
  567.         // track the frequency of each color...
  568.         var rgbColor = "" + pixels[p] + ", " + pixels[p+1] + ", "+ pixels[p+2] + "";
  569.         
  570.         var hsl = CHROMATABS.rgbToHsl(pixels[p], pixels[p+1], pixels[p+2]);
  571.         var hueOnly = hsl[0];
  572.         CHROMATABS.log("Hue is " + hueOnly);
  573.  
  574.         // see if it's in the list
  575.         var bFound = false
  576.         for (var j = 0; j < nrColors; j++) {
  577.             var targetRGB = arrColor[j].split(',');
  578.             var targetHSL = CHROMATABS.rgbToHsl(targetRGB[0], targetRGB[1], targetRGB[2] )
  579.             var targetHue = targetHSL[0];
  580.  
  581.             // old 2.2 code: exact match
  582.             // if (arrColor[j] == rgbColor) {
  583.  
  584.             // new 2.3 code: add some tolerance when matching
  585.             if (Math.abs((hueOnly - targetHue)/targetHue) <= (CHROMATABS._hueTolerance / 100)) { 
  586.  
  587.                 // in the list, so increment tally
  588.                 arrColorFreq[j] += 1;
  589.                 bFound = true;
  590.                 break; // don't need to keep looking
  591.             }
  592.         }
  593.         // not in the list, so add it
  594.         if (!bFound) {
  595.             arrColor[nrColors] = rgbColor
  596.             arrColorFreq[nrColors] = 1;
  597.             nrColors += 1;
  598.         }
  599.         
  600.     }
  601.     
  602.     
  603.     // find the most frequent color
  604.     var maxFreq = 2, mostPopularColor = '';
  605.     for (var j = 0; j < nrColors; j++) {
  606.         if (arrColorFreq[j] >= maxFreq) {
  607.             maxFreq = arrColorFreq[j];
  608.             mostPopularColor = arrColor[j];
  609.         }
  610.     }
  611.     CHROMATABS.log("Most popular color is " + mostPopularColor + " w/ a freq of " + maxFreq + " out of " + nrColors);
  612.  
  613.     // Get the pixels again, since we were not accessing a live version.
  614.     pixels = ctx.getImageData(0,0,1,1).data;
  615.  
  616.     var color;
  617.     if (CHROMATABS._colorMode == 'icon-frequency' && mostPopularColor != '') {
  618.         color = "rgba(" + mostPopularColor + ", 1)";
  619.     }
  620.     else { // icon-average
  621.         color = "rgba(" + pixels[0] + ", " + pixels[1] + ", "+ pixels[2] + ", 1)";
  622.     }
  623.  
  624.     // Cache the value for speed.
  625.     CHROMATABS._iconColorCache[imgsrc] = color;
  626.  
  627.     CHROMATABS.log("Computed color for " + imgsrc + ": " + color);
  628.  
  629.     return color;
  630.     },
  631.  
  632.  
  633.     //
  634.     // computeHostnameColor
  635.     //
  636.     // Given a <tab>, compute what color it should be.
  637.     //
  638.     computeHostnameColor : function (tab) {
  639.     var doc, host;
  640.  
  641.     function djb2hash(hashstring) {
  642.         var i, hashvalue = 5381;
  643.         for (i = 0; i < hashstring.length; i++) {
  644.             var ascii_code = hashstring.charCodeAt(i);
  645.             hashvalue = ((hashvalue << 5) + hashvalue) + ascii_code;
  646.         }
  647.         return hashvalue;
  648.     };
  649.  
  650.  
  651.     host = CHROMATABS.getHostnameForTab(tab);
  652.     if (!host) { return null; }
  653.  
  654.     // Compute a hash of the hostname, and clamp it to the 0-360 range allowed for the hue.
  655.     var hue = Math.abs(djb2hash(host)) % 360;
  656.     var sat = Math.abs(djb2hash(host + host)) % 90 + 10 ;
  657.     var lum = Math.abs(djb2hash(host + host + host)) % 75 + 25;
  658.  
  659.     CHROMATABS.log("Computed HSL as : " + hue + ", " + sat + ", " + lum);
  660.     // Make the color string. eg: rgba(180, 200, 300, 1)
  661.     var newRGB = CHROMATABS.hslToRgb(hue/360, sat/100, lum/100);
  662.     var color = "rgba(" + newRGB[0] + ", " + newRGB[1] + ", " + newRGB[2] + ", 1) "
  663.     CHROMATABS.log("... which converted into: " + color);
  664.     return color;
  665.     },
  666.  
  667.  
  668.     getHostnameForTab : function (tab) {
  669.     try {
  670.         // stupid about:blank
  671.         var host = tab.linkedBrowser.contentDocument.location.host;
  672.     } catch (e) {
  673.         return null;
  674.     }
  675.  
  676.     // Strip off any leading "www" so that "www.site.com" and "site.com" hash to the same color.
  677.     // (the 2 sites can be different, but this doesn't seem common)
  678.     var matches = /^www\.(.+\..+)$/.exec(host);
  679.     if (matches) { host = matches[1]; }
  680.     
  681.     // 2.3.0: If desired, treat all subdomains alike
  682.     CHROMATABS.log("Original host is: " + host);
  683.     
  684.     subdomainsTreatedEqually = CHROMATABS._prefs.getBoolPref("hashFallback.subdomainsTreatedEqually");
  685.     if (subdomainsTreatedEqually) {
  686.         // reduce subdomain.domain.tld  to just subdomain.domain.tld
  687.         var eTLDService = Components.classes["@mozilla.org/network/effective-tld-service;1"]
  688.                             .getService(Components.interfaces.nsIEffectiveTLDService);
  689.         var ioService = Components.classes["@mozilla.org/network/io-service;1"].getService();
  690.         var baseURI = ioService.newURI(tab.linkedBrowser.contentDocument.location.href, "UTF-8", null);
  691.  
  692.         host = eTLDService.getBaseDomain(baseURI)
  693.         CHROMATABS.log("Reduced host is: " + host);
  694.     }
  695.     
  696.  
  697.     return host;
  698.     },
  699.     
  700.     colorBottomBar :  function ()
  701.     {
  702.  
  703.     /* old version
  704.     gBrowser.mTabContainer.childNodes[(gBrowser.mPanelContainer.childNodes.length)-1].setAttribute("clrclass", clr);
  705.     //*******
  706.     //var tabtm = this.getElementsByAttribute("class","tabs-bottom").setAttribute("background-color","#00ff00");
  707.     */
  708.     //dump("\nColorfulTabs Log: setTaBottomClr");
  709.     var ss = new Array();
  710.     var ss = document.styleSheets;
  711.     for (var i=0; i < ss.length; i++)
  712.         {
  713.         switch (ss[i].href)
  714.             {
  715.             case 'chrome://chromatabs/skin/chromatabs.css':
  716.                 var clrSS = ss[i];
  717.                 break;
  718.             }
  719.         }
  720.     try
  721.         {
  722.         //clrSS.cssRules[2].style.setProperty ('background-color',document.defaultView.getComputedStyle(document.getAnonymousNodes(gBrowser.selectedTab)[0], null).getPropertyValue("background-color").toString() ,'important' );
  723.         clrSS.cssRules[2].style.setProperty ('background-color',document.defaultView.getComputedStyle(gBrowser.selectedTab, null).getPropertyValue("background-color").toString() ,'important' );
  724.         }
  725.     catch(e)
  726.         {
  727.         dump("\nctlog:\terror in function setTaBottomClr "+e);
  728.         }
  729.     },
  730.  
  731.     colorTreeStyleTabsBar :  function ()
  732.     {
  733.     var ss = new Array();
  734.     var ss = document.styleSheets;
  735.     for (var i=0; i < ss.length; i++)
  736.         {
  737.         switch (ss[i].href)
  738.             {
  739.             case 'chrome://chromatabs/skin/chromatabs.css':
  740.                 var clrSS = ss[i];
  741.                 break;
  742.             }
  743.         }
  744.     try
  745.         {
  746.         clrSS.cssRules[5].style.setProperty ('background-color',document.defaultView.getComputedStyle(gBrowser.selectedTab, null).getPropertyValue("background-color").toString() ,'important' );
  747.         }
  748.     catch(e)
  749.         {
  750.         dump("\nctlog:\terror in function colorTreeStyleTabsBar "+e);
  751.         }
  752.     },
  753.  
  754.     colorizeAllTabs :  function ()
  755.     {
  756.         var nrTabs = gBrowser.mTabs.length;
  757.         CHROMATABS.log("Found " + nrTabs + " tabs.");
  758.         
  759.         for (var i = 0; i < nrTabs; i++) {
  760.             var currTab = gBrowser.mTabs[i];
  761.             CHROMATABS.colorizeTab(currTab, false, true); // the last parameter says to not get the color from the cache
  762.         }
  763.     },
  764.  
  765.     restyleCloseButtons :  function ()
  766.     {
  767.         
  768.         var ss = new Array();
  769.         var ss = document.styleSheets;
  770.         for (var i=0; i < ss.length; i++)
  771.             {
  772.             switch (ss[i].href)
  773.                 {
  774.                 case 'chrome://chromatabs/skin/chromatabs.css':
  775.                     var clrSS = ss[i];
  776.                     break;
  777.                 }
  778.             }
  779.         try
  780.             {
  781.             if (CHROMATABS._clearTabCloseButtons) {
  782.                 // use alternative style
  783.                 clrSS.cssRules[7].style.setProperty ('list-style-image', 'url("chrome://chromatabs/skin/close_clear.png")' , '');
  784.                 clrSS.cssRules[7].style.setProperty ('-moz-image-region','rect(0px, 14px, 0px, 14px)' ,'' );
  785.                 clrSS.cssRules[7].style.setProperty ('opacity','0.9', '');
  786.                 clrSS.cssRules[8].style.setProperty ('list-style-image', 'url("chrome://chromatabs/skin/close_clear_down.png")' , '');
  787.                 clrSS.cssRules[8].style.setProperty ('-moz-image-region','rect(0px, 14px, 0px, 14px)' ,'' );
  788.                 clrSS.cssRules[8].style.setProperty ('opacity','0.9', '');
  789.             }
  790.             else {
  791.                 // use default system style
  792.                 clrSS.cssRules[7].style.removeProperty('list-style-image')
  793.                 clrSS.cssRules[7].style.removeProperty('-moz-image-region')
  794.                 clrSS.cssRules[7].style.removeProperty('opacity')
  795.                 clrSS.cssRules[8].style.removeProperty('list-style-image')
  796.                 clrSS.cssRules[8].style.removeProperty('-moz-image-region')
  797.                 clrSS.cssRules[8].style.removeProperty('opacity')
  798.             }
  799.             }
  800.         catch(e)
  801.             {
  802.             dump("\nctlog:\terror in function setTaBottomClr "+e);
  803.             }        
  804.     },
  805.  
  806.     // CREDIT FOR RGB <--> HSL CONVERSION CODE:
  807.     // Michael Jackson (no, a different one)
  808.     // http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
  809.     
  810.     /**
  811.      * Converts an RGB color value to HSL. Conversion formula
  812.      * adapted from http://en.wikipedia.org/wiki/HSL_color_space.
  813.      * Assumes r, g, and b are contained in the set [0, 255] and
  814.      * returns h, s, and l in the set [0, 1].
  815.      *
  816.      * @param   Number  r       The red color value
  817.      * @param   Number  g       The green color value
  818.      * @param   Number  b       The blue color value
  819.      * @return  Array           The HSL representation
  820.      */
  821.     rgbToHsl : function (r, g, b){
  822.         r /= 255, g /= 255, b /= 255;
  823.         var max = Math.max(r, g, b), min = Math.min(r, g, b);
  824.         var h, s, l = (max + min) / 2;
  825.  
  826.         if(max == min){
  827.             h = s = 0; // achromatic
  828.         }else{
  829.             var d = max - min;
  830.             s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
  831.             switch(max){
  832.                 case r: h = (g - b) / d + (g < b ? 6 : 0); break;
  833.                 case g: h = (b - r) / d + 2; break;
  834.                 case b: h = (r - g) / d + 4; break;
  835.             }
  836.             h /= 6;
  837.         }
  838.  
  839.         return [h, s, l];
  840.     },
  841.  
  842.     /**
  843.      * Converts an HSL color value to RGB. Conversion formula
  844.      * adapted from http://en.wikipedia.org/wiki/HSL_color_space.
  845.      * Assumes h, s, and l are contained in the set [0, 1] and
  846.      * returns r, g, and b in the set [0, 255].
  847.      *
  848.      * @param   Number  h       The hue
  849.      * @param   Number  s       The saturation
  850.      * @param   Number  l       The lightness
  851.      * @return  Array           The RGB representation
  852.      */
  853.      
  854.     hslToRgb: function (h, s, l){
  855.         var r, g, b;
  856.  
  857.         if(s == 0){
  858.             r = g = b = l; // achromatic
  859.         }else{
  860.             function hue2rgb(p, q, t){
  861.                 if(t < 0) t += 1;
  862.                 if(t > 1) t -= 1;
  863.                 if(t < 1/6) return p + (q - p) * 6 * t;
  864.                 if(t < 1/2) return q;
  865.                 if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
  866.                 return p;
  867.             }
  868.  
  869.             var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
  870.             var p = 2 * l - q;
  871.             r = hue2rgb(p, q, h + 1/3);
  872.             g = hue2rgb(p, q, h);
  873.             b = hue2rgb(p, q, h - 1/3);
  874.         }
  875.  
  876.         return [r * 255, g * 255, b * 255];
  877.     }
  878.  
  879. };
  880.  
  881. window.addEventListener("load", function () { CHROMATABS.init(); }, false);
  882.